package glemipsc;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.*;

public class ObjectFile implements SymbolTable, Serializable {
	private static ArrayList<ObjectFile> objectFiles = new ArrayList<ObjectFile>();

	private String sourceName, objectName;
	private HashMap<String,SymbolElement> symbols = new HashMap<String,SymbolElement>();
	private SymbolTable nestedSymbolTable = null;
	private int errors = 0;
	
	public ObjectFile(String sourceName) {
		this.sourceName = sourceName;
		objectName = sourceName.substring(0, sourceName.lastIndexOf(".")) + ".o";
		
		if (!GLEMipsC.compiler.environment.saveObjectFiles)
			objectFiles.add(this);
	}

	public String getSourceName() {
		return sourceName;
	}

	public String getObjectName() {
		return objectName;
	}

	public void addSymbol(SymbolElement e) {
		if (exists(e.getName())) {
			if (e instanceof glemipsc.Function && get(e.getName()) instanceof glemipsc.Function) {
				Function tmp = (Function) e;
				Function present = (Function) get(e.getName());
				if (tmp.isCoded() && !present.isCoded() && tmp.equals(present)) {
					tmp.replace(present);
					return;
				}else if(!tmp.isCoded() && present.isCoded() && tmp.equals(present)){
					// Prototipo dopo la dichiarazione della funzione
					// Quando vengono incluse librerie siamo in questo caso
					return;
				}else if (tmp.isCoded() && !present.isCoded() && !tmp.equals(present)) {
					GLEMipsC.compiler.displayError(ErrorLevel.ERROR, "Definition of function " + tmp.getName() + " doesn't match it's prototype");
					return;
				}
			}
			else {
				GLEMipsC.compiler.displayError(ErrorLevel.ERROR, "Symbol " + e.getName() + " already defined in the same scope");
				return;
			}
		}
		if (nestedSymbolTable != null)
			nestedSymbolTable.addSymbol(e);
		else {
			symbols.put(e.getName(), e);
			if (e instanceof Function)
				((Function) e).setObjectFile(this);
		}
	}

	public void addSymbols(List es) {
		if (nestedSymbolTable != null) {
			nestedSymbolTable.addSymbols(es);
			return;
		}
		if (es == null)
			return;
		Iterator iter = es.iterator();
		while (iter.hasNext()) {
			SymbolElement el = (SymbolElement) iter.next();
			addSymbol(el);
		}
	}
	
	public boolean exists(String name) {
		if (nestedSymbolTable != null)
			return nestedSymbolTable.exists(name);
		return symbols.get(name) != null;
	}
	
	public SymbolElement get(String name) {
		if (nestedSymbolTable != null) {
			SymbolElement el = nestedSymbolTable.get(name);
			if (el != null)
				return el;
		}
		return symbols.get(name);
	}

	public void pushSymbolTable(SymbolTable table) {
		nestedSymbolTable = table;
	}
	public SymbolTable popSymbolTable() {
		SymbolTable table = nestedSymbolTable;
		nestedSymbolTable = null;
		return table;
	}
	
	public Function getFunction() {
		if (nestedSymbolTable instanceof Function)
			return (Function) nestedSymbolTable;
		return null;
	}

	public static SymbolElement getGlobalElement(String name) {
		Iterator<ObjectFile> iter = objectFiles.iterator();
		while (iter.hasNext()) {
			ObjectFile objFile = iter.next();
			SymbolElement e = objFile.get(name);
			if (e == null)
				continue;
			if (e.getType().isStatic())
				continue;
			return e;
		}
		return null;
	}
	
	public List<Function> getCodedFunctions() {
		ArrayList<Function> list = new ArrayList<Function>();
		Iterator<String> iter = symbols.keySet().iterator();
		while (iter.hasNext()) {
			SymbolElement el = symbols.get(iter.next());
			if (!(el instanceof Function))
				continue;
			Function f = (Function) el;
			if (!f.isCoded())
				continue;
			list.add(f);
		}
		return list;
	}
	
	public void linkFunctions(Linker linker) {
		Iterator<String> iter = symbols.keySet().iterator();
		while (iter.hasNext()) {
			SymbolElement el = symbols.get(iter.next());
			if (!(el instanceof Function))
				continue;
			Function f = (Function) el;
			if (f.isCoded())
				continue;
			Function coded = linker.getFunction(f.getName());
			if (coded == null) {
				GLEMipsC.compiler.displayError(ErrorLevel.ERROR, "Prototyped function " + f.getName() + " not defined");
				return;
			}
			f.setStoreAddress(coded.getStoreAddress());
		}
	}
	
	public List<SymbolElement> getStaticVariables() {
		ArrayList<SymbolElement> list = new ArrayList<SymbolElement>();
		Iterator<String> iter = symbols.keySet().iterator();
		while (iter.hasNext()) {
			SymbolElement el = symbols.get(iter.next());
			if (el instanceof Function) {
				Function f = (Function) el;
				if (!f.isCoded())
					continue;
				list.addAll(f.getStaticVariables());
				continue;
			}
			else if (el.getType().getStorageType() == Type.StoreType.EXTERN)
				continue;
			list.add(el);
		}
		return list;
	}
	
	public void linkVariables(Linker linker) {
		Iterator<String> iter = symbols.keySet().iterator();
		while (iter.hasNext()) {
			SymbolElement el = symbols.get(iter.next());
			if (el instanceof Function)
				continue;
			if (el.getType().getStorageType() != Type.StoreType.EXTERN)
				continue;
			SymbolElement var = linker.getGlobalVariable(el.getName());
			if (var == null) {
				// TODO: Linker: Extern variable not defined error
				return;
			}
			el.setStoreAddress(var.getStoreAddress());
		}
	}
	
	public void save() {
		FileOutputStream fos;
		try {
			fos = new FileOutputStream(objectName);
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(this);
			oos.close();
			fos.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static ObjectFile load(String name) {
		FileInputStream fis;
		try {
			fis = new FileInputStream(name);
			ObjectInputStream ois = new ObjectInputStream(fis);
			ObjectFile objFile = (ObjectFile) ois.readObject();
			ois.close();
			fis.close();
			objectFiles.add(objFile);
			return objFile;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			return null;
		}
	}
	
	public static boolean isLoaded(String name) {
		Iterator<ObjectFile> iter = objectFiles.iterator();
		while (iter.hasNext()) {
			ObjectFile objFile = iter.next();
			if (objFile.getObjectName().equals(name))
				return true;
		}
		return false;
	}
	
	public static List<ObjectFile> getObjectFiles() {
		return objectFiles;
	}
	
	public void addError() {
		errors++;
	}
	
	public int getErrors() {
		return errors;
	}
}
